using System;
using EnvDTE;
using AopDotNet.CodeModelXMLTreeBuilder;
using AopDotNet.Editor;
using System.Xml ;
using System.Windows.Forms;
using System.Xml.XPath  ;
using System.Text .RegularExpressions ;
using System.Text ;
using System.Collections ;
using System.CodeDom .Compiler ;
using System.CodeDom ;
using System.IO ;

namespace AopDotNet
{
	namespace JoinPointSearchEngine
	{
		public struct VarInfo
		{
			public VarInfo(string s , int p)
			{
				varName=s;
				varPos=p;
			}
			public string varName;

			public int varPos;//-1 class members
			//0 parameters
		}
		public struct AdviceInfo
		{
			///<editdate>9/30/2003 8:56:17 PM</editdate>
			/// <summary>
			/// 
			/// </summary>
			/// <param name="start">the Index of the start of the code that represents a join Point</param>
			/// <param name="end">the Index of the end of the code that represents a join Point</param>
			/// <param name="node">the Code Model Method node that contains the code of the join point</param>
			/*	public AdviceInfo(int start,int len,XmlNode node)
				{
					startIndex=start;
					length=len;
					advisedNode=node;
				}*/

			public AdviceInfo(int start,int end,XmlNode node)
			{
				//startIndex=start;
				startPointId =start;
				//length=len;
				endPointId=end;
				advisedNode=node;
			}

			//public int startIndex;
			//public int length;
			public int startPointId;
			public int endPointId;
			public XmlNode advisedNode;
		}

		/// <summary>
		/// Summary description for Class1.
		/// </summary>
		public class JPSearchEngine
		{
			private string currentAspectName,currentAdviceType,currentAdviceFuncName,currentQueryString;
			private	ArrayList currentNodesInfoToAdvise;
			private ICodeGenerator codeGenerator;
			public Hashtable markedEditPoints;
			public CodeModelBuilder codeModel;
			static string varPattern=@"\s*{<R>}?<varname>[a-zA-Z_]\w*{<L>}\s*";
			private EditingMonitor editingMonitor;
			public JPSearchEngine(CodeModelBuilder cm,ICodeGenerator codgen, EditingMonitor editMon)
			{
				editingMonitor=editMon;
				codeModel=cm;
				codeGenerator =codgen;
				currentNodesInfoToAdvise=new ArrayList (20);
				markedEditPoints=new Hashtable (40);
			}
		
			public void StartSearch(string aspectDescriptorFile)
			{
				XmlDocument aspectDescriptor=new XmlDocument ();
				try
				{
					aspectDescriptor .Load (aspectDescriptorFile);/*Error Handling*/
				}
				catch (XmlException exp)
				{
					MessageBox.Show ("Error parsing aspectDescriptor.xml: \n"+exp.Message,"AopDotNetAddIn" );
				}
		
				XmlNodeList AspectNodes=aspectDescriptor.SelectNodes ("//aspect[@weave='true']");
				for (int i=0;i<AspectNodes.Count ;i++)
				{
					currentAspectName=AspectNodes[i].Attributes ["name"].Value ;
				
					ProcessAdvices(AspectNodes[i]);
				
					ProcessIntroductions(AspectNodes[i]);
				}
			}
			private void ProcessAdvices(XmlNode AspectNode)
			{
				XmlNode AdvicesNode=AspectNode.SelectSingleNode ("advices");
				if (AdvicesNode!=null)
				{
					XmlNodeList AdviceNodes=AdvicesNode.ChildNodes ;
					for (int j=0;j<AdviceNodes.Count;j++)
					{
						currentAdviceType =AdviceNodes[j].Attributes ["type"].Value  ;//change when schema
						currentAdviceFuncName =AdviceNodes[j].SelectSingleNode ("method").Attributes ["name"].Value  ;
						//currentAdviceFuncId=AdviceNodes[j].SelectSingleNode ("method").Attributes ["id"].Value  ;
						currentQueryString="";

						//find the places that should be advised 
						ResolvePointCut(AdviceNodes[j].SelectSingleNode ("pointcut") );
						AdviseCode( );
					}
				}
			}

			void ProcessIntroductions(XmlNode AspectNode)
			{
				XmlNode introductionsNode=AspectNode.SelectSingleNode ("introductions");
				if (introductionsNode==null)
					return;
				XmlNodeList introductionsNodes=introductionsNode.ChildNodes ;
				for (int j=0;j<introductionsNodes.Count ;j++)
				{
					currentQueryString="";
					switch(introductionsNodes [j].Name )
					{
						case "method":
							currentQueryString="//class";
							ModifyTokenForXPathQuery(introductionsNodes[j].Attributes ["introduceto"].Value,"@name");
							break;
						case "member":
							currentQueryString="//class";
							ModifyTokenForXPathQuery(introductionsNodes[j].Attributes ["introduceto"].Value,"@name");
							break;
						case "attribute":
							currentQueryString="//class";
							ModifyTokenForXPathQuery(introductionsNodes[j].Attributes ["introduceto"].Value,"@name");
							XmlAttribute a=(XmlAttribute)introductionsNodes[j].Attributes.GetNamedItem ("method");
							if (a!=null)
							{
								currentQueryString+="/descendant::method";
								ModifyTokenForXPathQuery(a.Value,"@name");
							}
							else
							{
								a=(XmlAttribute)introductionsNodes[j].Attributes.GetNamedItem ("member");
								if (a!=null)
								{
									currentQueryString+="/descendant::member";
									ModifyTokenForXPathQuery(a.Value,"@name");
								}
							}
							break;
						case "interface":
							string introScope="class";/*introductionsNodes[j].Attributes ["introductionscope"].Value;*/
							string name=introductionsNodes[j].Attributes ["name"].Value;
							string parentNameSpace="";
							try
							{parentNameSpace=codeModel.GetXmlDocument ().SelectSingleNode ("//interface[@name='"+name+"']/ancestor::namespace/@name").Value ;}
							catch(Exception excep)
							{
							}

							string introduceTo=introductionsNodes[j].Attributes ["introduceto"].Value;

							//if ((introScope!="namespace")||(parentNameSpace==introduceTo))
							//{
							currentQueryString="//"+introScope;
							ModifyTokenForXPathQuery(introduceTo,"@name");
							//}
							//currentQueryString +=
							break;
						case "constructor":
							currentQueryString="//class";
							ModifyTokenForXPathQuery(introductionsNodes[j].Attributes ["introduceto"].Value,"@name");
							break;
					}
					IntroduceCode(currentQueryString,currentAspectName ,introductionsNodes[j]);
				}
			}
			protected void ResolvePointCut(XmlNode pointCut)
			{
				XmlNodeList pointCuts=pointCut.ChildNodes ;
				foreach (XmlNode p in pointCuts)
				{
					switch (p.Name )
					{
						case "or":
							break;
						case "and":
							//recursive call resolve pointcut
							break;
						case "execution":
							ResolveExecutionJp(p.FirstChild );
							break;
						case "call":
							ResolveCallJp(p.FirstChild );
							break;
					}
				}
			}
			protected void ResolveExecutionJp(XmlNode joinPoint)					
			{
				int index=0;
				currentQueryString+="//method";
				string methodName=joinPoint.Attributes ["name"].Value ;
				string methodClass=joinPoint.Attributes ["class"].Value ;
				string methodAccess=joinPoint.Attributes ["access"].Value ;
				string methodReturn=joinPoint.Attributes ["return"].Value ;

				if ((methodClass!=null )&&( methodClass!="*"))
				{
					index=methodClass.IndexOf ('*');
				
					if ((index!=-1)&&(index!=0))
						currentQueryString+="[ancestor::class[starts-with(@name ,'"+methodClass.Substring (0,index)+"')]]";
					else
						if (index==-1)
						currentQueryString+="[ancestor::class[@name ='"+methodClass+"']]";
				}
				ModifyTokenForXPathQuery(methodName,"@name");
				ModifyTokenForXPathQuery(methodAccess,"@access");
				ModifyTokenForXPathQuery(methodReturn,"@return");
				XmlNode argsNode=joinPoint.SelectSingleNode ("args");
				if (argsNode!=null)
				{
					foreach(XmlNode argNode in argsNode.ChildNodes )
					{
						currentQueryString +="[parameters/parameter";

						try
						{
							int pos=Int32.Parse (argNode.Attributes ["position"].Value );
							currentQueryString +="["+pos.ToString ()+"]";
						}
						catch(Exception e)
						{
						}
						finally
						{
							currentQueryString+="/@type='"+argNode.Attributes ["type"].Value +"']";
						}
					}
				}
			}
			/// <editdate>9/30/2003 8:57:39 PM</editdate>
			protected void ResolveCallJp(XmlNode joinPoint )
			{
				int index=0;
				currentQueryString+="";
			
				string methodName=joinPoint.Attributes ["name"].Value ;
				string methodClass=joinPoint.Attributes ["class"].Value ;
				string methodAccess=joinPoint.Attributes ["access"].Value ;//not necessary in call
				string methodReturn=joinPoint.Attributes ["return"].Value ;// not necessary in call

				string strCallPattern=GenerateMethodCallPattern(methodClass,methodName,joinPoint );

				//CodeMethodReferenceExpression
				StringWriter writer;
				XmlDocument codeModelDoc=codeModel.GetXmlDocument ();
				XmlNodeList classesNodes=codeModelDoc.SelectNodes ("//class");
			
				//generate all var declare or assign or param the type and then find the methods call
				//this.method must be in the same class store these patterns in array
				foreach(XmlNode curClassNode in classesNodes)
				{
					ArrayList variablesCollection=new ArrayList (30);
					if (curClassNode.Attributes ["name"].Value ==methodClass)
					{
						//this
						writer=new StringWriter();
						codeGenerator.GenerateCodeFromExpression (new CodeThisReferenceExpression (),writer,null);
						VarInfo var=new VarInfo (writer.ToString (),-1);
						variablesCollection.Add (var);
					}
				
					GetVariableDeclaredWithThisTypeInClass(curClassNode,methodClass,ref variablesCollection);
					int classLevelVariables=variablesCollection.Count ;

					XmlNodeList methodsNodes=curClassNode.SelectNodes ("methods/method");
					foreach(XmlNode curMethodNode in methodsNodes)
					{
						CodeFunction theCurMethod=(CodeFunction)GetCodeElementFromXmlNode(curMethodNode,codeModel );
						string strCurMethod=codeModel.GetFunctionBody(theCurMethod);
						GetVariableDeclaredWithThisTypeInMethod(curMethodNode,strCurMethod,methodClass,ref variablesCollection);
				
						///////////////////////////
						///////////////////////////
						///////////////////////////
						//////////////////////////
						///////////////////////////
						MatchCollection matchedCalls=Regex.Matches (strCurMethod,strCallPattern);
						foreach(Match matchedCall in matchedCalls )
						{
							string matchedObject=matchedCall.Groups ["varname"].Value ;
							string matchedParameters=matchedCall.Groups ["params"].Value ;
							int varPos;
							if (IsVariableInCollection (matchedObject,ref variablesCollection,out varPos))
							{
								if (varPos<matchedCall.Index )
								{
									//use AdviseCode to add this node to the advised code model
									EditPoint startPoint=theCurMethod.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint ();
									/*REMF*/
									startPoint.MoveToAbsoluteOffset (theCurMethod.GetStartPoint(vsCMPart.vsCMPartBody).AbsoluteCharOffset +matchedCall.Index );
																
									//currentNodesInfoToAdvise.Add (new AdviceInfo (matchedCall.Index  ,matchedCall.Length ,curMethodNode));
								
									//startPoint .Insert ("/*bef*/");
									EditPoint endPoint=startPoint.CreateEditPoint ();
									endPoint.MoveToAbsoluteOffset(endPoint.AbsoluteCharOffset +matchedCall.Length );
									//endPoint.Insert ("/*aft*/");
									markedEditPoints.Add (startPoint.GetHashCode (),startPoint);
									markedEditPoints.Add (endPoint.GetHashCode (),endPoint);
									currentNodesInfoToAdvise.Add (new AdviceInfo (startPoint.GetHashCode (),endPoint.GetHashCode (),curMethodNode));
								}
							}
						}
						//foreach(string variableName in variableNames )
						//{
						//generate call pattern
						//}

						//remove method level varialbes
						variablesCollection.RemoveRange (classLevelVariables,variablesCollection.Count -classLevelVariables);
					}
					//search the function body for patterns strored in the array
					//if appatern match then search for a call to the specified function
				}
				//build the query string
			}
			void GetVariableDeclaredWithThisTypeInMethod(XmlNode theMethod,string strMethod,string TypeName,ref ArrayList variablesCollection)
			{
				string theType;
				int index=TypeName.IndexOf ('*');
				if (index==-1)
					theType=@"\s*"+TypeName+@"\s*";
				else
					theType=@"\s*{<R>}?<type>{<R>}"+TypeName.Substring (0,index)+@"{<R>}\w{<L>}*{<L>}{<L>}\s*";

				StringWriter writer=new StringWriter();
			
				//check parameters
				XmlNodeList parametersNodes=theMethod.SelectNodes ("parameters/parameter");
				foreach(XmlNode parameterNode in parametersNodes)
				{
					string type=parameterNode.Attributes ["type"].Value ;
					Match m=Regex.Match (type,theType);
					if (m.Success )
					{
						VarInfo var=new VarInfo (parameterNode.Attributes ["name"].Value,0);
						variablesCollection.Add (var);
					}

				}
				//check variable declarations in code
			
				CodeStatementCollection statements=new CodeStatementCollection ();

				//employee x; | Dim x as employee
				statements .Add (new CodeVariableDeclarationStatement (theType,varPattern));

				//employee x=somthing; | Dim x as employee=somthing
				statements .Add (new CodeVariableDeclarationStatement (theType,varPattern,new CodeSnippetExpression (@"\s*{<R>}?<initexpr>{<R>}\s|\w|.{<L>}*{<L>}\s*")));

				CodeCastExpression castExpr=new CodeCastExpression (theType,new CodeSnippetExpression (@"\s*{<R>}?<initexpr>{<R>}\s|\w|.{<L>}*{<L>}\s*"));
				//x=(employee)somthing | x=CType(somthing,employee)
				statements .Add (new CodeAssignStatement (new CodeSnippetExpression (varPattern),castExpr));
				CodeObjectCreateExpression createExp=new CodeObjectCreateExpression (theType,new CodeExpression []{new CodeSnippetExpression (@"\s*{<R>}?<parameters>{<R>}\s|\w|.{<L>}*{<L>}\s*")});
				writer=new StringWriter();
				codeGenerator .GenerateCodeFromExpression (createExp,writer,null);
				CodeSnippetExpression createExp2=new CodeSnippetExpression (@"\s*"+writer.ToString ()+@"\s*");
				statements .Add (new CodeAssignStatement (new CodeSnippetExpression (varPattern),createExp2));

				CodeVariableDeclarationStatement  ST=new CodeVariableDeclarationStatement (theType,varPattern,new CodeObjectCreateExpression (theType,new CodeExpression []{new CodeSnippetExpression (@"\s*{<R>}?<initexpr>{<R>}\s|\w|.{<L>}*{<L>}\s*")}));
				statements .Add (ST);


				//specific to VB:  Dim x as New Employee   /*Missing*/ Dim x as new Employee(22)
				createExp=new CodeObjectCreateExpression (theType);
				writer=new StringWriter ();
				codeGenerator.GenerateCodeFromExpression (createExp,writer,null);
				ST=new CodeVariableDeclarationStatement (writer.ToString (),varPattern);
				statements .Add (ST);
			

		
				foreach (CodeStatement st in statements)
				{
					writer=new StringWriter ();
					codeGenerator.GenerateCodeFromStatement(st,writer,null);
					string res=PrepareStringForRegexMatch(writer.ToString ());
					//res=res.Trim ();
				
					MatchCollection m=Regex.Matches (strMethod,res,RegexOptions .ExplicitCapture );
					foreach(Match matched in m)
					{
						VarInfo var=new VarInfo (matched.Groups ["varname"].Value, matched.Index );
						variablesCollection.Add (var);
					
					}
				}
			}
			void GetVariableDeclaredWithThisTypeInClass(XmlNode classNode,string theType,ref ArrayList variableNames)
			{
				//get member variables that have the Type and members of the base class
			
				string query="membervariables/member";
				int index;
				if ((theType!=null )&&( theType !="*"))
				{
					index=theType.IndexOf ('*');
				
					if ((index!=-1)&&(index!=0))
						query+="[starts-with(@type,'"+theType.Substring (0,index)+"')]";
					else
						if (index==-1)
						query+="[@type='"+theType +"']"; /*Error Handling*/ //type is preceeded with the namespace in the xml file
				}
			
				XmlNodeList members=classNode.SelectNodes (query);
				foreach(XmlNode member in members )
				{
					VarInfo var=new VarInfo (member.Attributes ["name"].Value,-1);
					variableNames.Add (var );
				}
			}

			static bool IsVariableInCollection(string varName,ref ArrayList variables,out int varPos)
			{
				int pos=-2;
				foreach(VarInfo var in variables)
				{
					if (var.varName ==varName)
					{
						if (var.varPos >pos)
							pos=var.varPos ;
					}
				}
				varPos=pos;
				if (pos==-2)
					return false;
				else
					return true;
			}

			static  CodeElement GetCodeElementFromXmlNode(XmlNode theNode,CodeModelBuilder codMod)
			{
				int id=Int32.Parse (theNode.Attributes ["id"].Value);
				if (codMod.codeElements.Contains (id)==false)
					return null ;/*Error Handling*/
				else
					return (CodeElement)codMod.codeElements [id];
			}
			protected void ModifyTokenForXPathQuery(string token,string key)
			{
				int index;
				if ((token!=null )&&( token!="*"))
				{
					index=token.IndexOf ('*');
				
					if ((index!=-1)&&(index!=0))
						currentQueryString+="[starts-with("+key+" ,'"+token .Substring (0,index)+"')]";
					else
						if (index==-1)
						currentQueryString+="["+key+" ='"+token +"']";
				}
			}
			public void IntroduceCode(string queryString,string aspectName ,XmlNode introductionNode)
			{
				XmlDocument codeModelDoc=codeModel.GetXmlDocument ();
				XmlNodeList res=codeModelDoc.SelectNodes (queryString );
				foreach(XmlNode node in res)
				{
					CreateAnIntroductionNode (node,aspectName,introductionNode);
				}
			}


			/// <summary>
			/// 
			/// </summary>
			/// <param name="node">the element in the base code that is being advised</param>
			/// <param name="aspectName"></param>
			/// <param name="adviceType"></param>
			/// <param name="adviceFucnName"></param>
			/// <param name="startIndex">the index of the start point of the piece of code that is being advised</param>
			/// <param name="length">the length of the code</param>
			/// <editdate>9/30/2003 8:59:52 PM</editdate>
			private void CreateAnAdviceNode(XmlNode node,string aspectName,string adviceType,string adviceFuncName,int startPointId,int endPointId)
			{
				XmlDocument codeModelDoc=codeModel.GetXmlDocument ();
				XmlNode advicesChild=node.SelectSingleNode ("advices");
				if (advicesChild==null)
				{
					advicesChild=codeModelDoc.CreateElement ("advices");
					node.AppendChild (advicesChild);
				}
				XmlElement advice=codeModelDoc.CreateElement ("advice");
				advicesChild.AppendChild (advice);

				XmlAttribute adviceTypeAtt,adviceFuncNameAtt,aspectNameAtt;
				adviceTypeAtt=codeModelDoc.CreateAttribute("type");
				adviceFuncNameAtt=codeModelDoc.CreateAttribute("method");
				aspectNameAtt=codeModelDoc.CreateAttribute("aspect");

				adviceTypeAtt.Value =adviceType;
				adviceFuncNameAtt.Value =adviceFuncName;
				aspectNameAtt.Value =aspectName;
				
				advice.SetAttributeNode (adviceTypeAtt);
				advice.SetAttributeNode (adviceFuncNameAtt);
				advice.SetAttributeNode (aspectNameAtt);
				//if (startIndex !=-1&&length!=-1)
				if (startPointId !=-1&&endPointId!=-1)
				{
					XmlAttribute startPointAtt,endPointAtt;
					startPointAtt=codeModelDoc.CreateAttribute("startpointid");
					endPointAtt=codeModelDoc.CreateAttribute("endpointid");
					startPointAtt.Value =startPointId.ToString ();
					endPointAtt.Value =endPointId.ToString ();
					advice.SetAttributeNode(startPointAtt);
					advice.SetAttributeNode(endPointAtt);
				}
				
			}

			private void CreateAnIntroductionNode(XmlNode node,string aspectName,XmlNode introductionNode)
			{
				XmlDocument codeModelDoc=codeModel.GetXmlDocument ();
				XmlNode introductionsChild=node.SelectSingleNode ("introductions");
				if (introductionsChild==null)
				{
					introductionsChild=codeModelDoc.CreateElement ("introductions");
					node.AppendChild (introductionsChild);
				}
				introductionNode=codeModelDoc.ImportNode (introductionNode,true) ;
				introductionsChild.AppendChild (introductionNode );
			
				XmlAttribute aspectNameAtt;
				aspectNameAtt=codeModelDoc.CreateAttribute("aspect");
				aspectNameAtt.Value =aspectName;
				
				XmlAttribute introduceToAtt=introductionNode.Attributes ["introduceto"];
				introductionNode.Attributes.Remove (introduceToAtt );
				
				((XmlElement )introductionNode).SetAttributeNode (aspectNameAtt);
			
			}
		
			public void AdviseCode()
			{
				if (currentQueryString!="")
				{
					XmlDocument codeModelDoc=codeModel.GetXmlDocument ();
					XmlNodeList res=codeModelDoc.SelectNodes (currentQueryString  );
					foreach(XmlNode node in res)
					{
						CreateAnAdviceNode(node,currentAspectName ,currentAdviceType ,currentAdviceFuncName ,-1,-1);
					}
				}
				else
				{
					//no xpath expression to execute so advise the AdviceInfo objects
					foreach (AdviceInfo info in currentNodesInfoToAdvise)
						CreateAnAdviceNode(info.advisedNode,currentAspectName ,currentAdviceType ,currentAdviceFuncName ,info.startPointId ,info.endPointId  );
				
				}
			}

			protected string GenerateMethodCallPattern(string methodClassName,string methodName,XmlNode joinPoint)
			{
				int index=0;
				string method="";

						
				index=methodName.IndexOf ('*');
				if (index==-1)
					method=@"\s*"+methodName+@"\s*";
				else
					method=@"\s*{<R>}?<method>{<R>}"+methodName.Substring (0,index)+@"{<R>}\w{<L>}*{<L>}{<L>}\s*";

				////////////////////////////
				XmlNode argsNode=joinPoint.SelectSingleNode ("args");
				if (argsNode!=null)
				{
					foreach(XmlNode argNode in argsNode.ChildNodes )
					{
						currentQueryString +="[parameters/parameter";
						try
						{
							int pos=Int32.Parse (argNode.Attributes ["position"].Value );
							currentQueryString +="["+pos.ToString ()+"]";
						}
						catch(Exception e)
						{
						}
						finally
						{
							currentQueryString+="/@type='"+argNode.Attributes ["type"].Value +"']";
						}
					}
				}
				//////////////////////////////////
				CodeExpression [] parameters=new CodeExpression []{new CodeSnippetExpression  (@"\s*{<R>}?<params>{<R>}\s|\w|\.{<L>}*{<L>}\s*")};
				//generate the call pattern
				CodeMethodInvokeExpression callPattern=new CodeMethodInvokeExpression (new CodeSnippetExpression (varPattern),method ,parameters);
				CodeExpressionStatement callStatement=new CodeExpressionStatement (callPattern);
				StringWriter writer=new StringWriter ();
				codeGenerator .GenerateCodeFromStatement(callStatement,writer,null);
				return PrepareStringForRegexMatch(writer.ToString());
			
			}
			static string PrepareStringForRegexMatch(string inputString)
			{
				StringBuilder strInput=new StringBuilder (inputString.Trim ());

				strInput.Replace ("(","[(]");
				strInput.Replace (")","[)]");
				strInput.Replace ("{<R>}","(");
				strInput.Replace ("{<L>}",")");
				Match match=Regex.Match(strInput.ToString () ,@"\s+");

				while (match.Success )
				{
					char before=strInput [match.Index-1];
					char after=strInput [match.Index +match.Length ];
					Match m1=Regex.Match (new string(before,1),"[a-zA-Z_0-9]");
					Match m2=Regex.Match (new string(after,1),"[a-zA-Z_0-9]");
					if (!(m1.Success & m2.Success ))
					{
						strInput.Replace (match.Value ,@"\s*",match.Index ,match.Length );
					}
					match=match.NextMatch ();
				}
				return strInput.ToString () ;
			}
		}
	}
}